//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
//==
//==
//==
//==================================================================

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "m5d_camera.h"
#include "m5d_utils_ext.h"

//==================================================================
namespace M5D
{

//==================================================================
Camera::Camera( Scene *scenep, const char *namep, Camera *copy_fromp ) :
	Model(Model::TYPE_CAMERA, scenep, namep),
	_type(CAMERA_TYPE_PERSPECTIVE)
{
	if ( copy_fromp )
	{
		_type			= copy_fromp->_type;
		_projection_m	= copy_fromp->_projection_m;
		_viewport[0]	= copy_fromp->_viewport[0];
		_viewport[1]	= copy_fromp->_viewport[1];
		_viewport[2]	= copy_fromp->_viewport[2];
		_viewport[3]	= copy_fromp->_viewport[3];
		_viewport_minmax_z[0] = copy_fromp->_viewport_minmax_z[0];
		_viewport_minmax_z[1] = copy_fromp->_viewport_minmax_z[1];
	}
	else
	{
		_projection_m.Identity();
		_viewport[0] = 0;
		_viewport[1] = 0;
		_viewport[2] = 640;
		_viewport[3] = 480;
		_viewport_minmax_z[0] = 1;
		_viewport_minmax_z[1] = 1000;
	}
}

//==================================================================
Camera::~Camera()
{
}

//==================================================================
void Camera::SetViewport( int x, int y, int w, int h, float min_z, float max_z )
{
	_viewport[0] = x;
	_viewport[1] = y;
	_viewport[2] = w;
	_viewport[3] = h;
	_viewport_minmax_z[0] = min_z;
	_viewport_minmax_z[1] = max_z;
}

//==================================================================
void Camera::SetFrustum( float l, float r, float b, float t, float n, float f )
{
float	rl, tb, fn;

	if ( (rl = r - l) == 0 || (tb = t - b) == 0 || (fn = f - n) == 0 )
		return;

	Matrix44	&m = _projection_m;

	m._m11 = 2 * n / rl; m._m21 = 0;          m._m31 =  (r + l) / rl; m._m41 = 0; 
	m._m12 = 0;          m._m22 = 2 * n / tb; m._m32 =  (t + b) / tb; m._m42 = 0; 
	m._m13 = 0;          m._m23 = 0;          m._m33 = -(f + n) / fn; m._m43 = -2 * f * n / fn;
	m._m14 = 0;          m._m24 = 0;          m._m34 = -1;            m._m44 = 0; 
}

//==================================================================
void Camera::SetPerspectiveProj( float fovy, float aspect, float znear, float zfar )
{
	float	ang = fovy * 0.5f;
	float	f = cosf( ang ) / sinf( ang );
	
	_projection_m = Matrix44(
		f/aspect,	0,			0,								0,
		0,			f,			0,								0,
		0,			0,			(zfar+znear) / (znear-zfar),	-1,
		0,			0,			2*zfar*znear / (znear-zfar),	0	);

/*
float	xmin, xmax, ymin, ymax;

	float	ang = fovy * DEG2RAD * 0.5f;

	ymax = n * cosf( ang ) / sinf( ang );
	ymin = -ymax;

	xmin = ymin * aspect;
	xmax = ymax * aspect;

	SetFrustum( xmin, xmax, ymin, ymax, n, f );
*/
}

//==================================================================
void Camera::SetOrthogonalProj( float x1, float x2, float y2, float y1, float n, float f )
{
	_projection_m = Matrix44::OrthogonalProj( x1, x2, y2, y1, n, f );
}

//===================================================
static Vector3 getHRayPlaneIntersect( const Vector3 &ray_from, const Vector3 &ray_dir,
									  const Vector4 &plane_norm,
									  bool *does_intersectp )
{
	float	t1, t2, t;

	//---------
	if NOT( t2 = plane_norm.GetDot( ray_dir ) )
	{
		PASSERT( 0 );
		if ( does_intersectp )
			*does_intersectp = false;

		return Vector3( 0, 0, 0 );
	}

	float	oo_t2 = 1.0f / t2;

	t1 = -(ray_from._x * plane_norm._x +
		   ray_from._y * plane_norm._y +
		   ray_from._z * plane_norm._z +
					 1 * plane_norm._w);

	t = t1 * oo_t2;
	if ( t >= 0.000001f )
	{
		if ( does_intersectp )
			*does_intersectp = true;

		return ray_from + ray_dir * t;
	}
	else
	{
		if ( does_intersectp )
			*does_intersectp = false;

		return Vector3( 0, 0, 0 );
	}
}

//==================================================================
Vector3	Camera::GetPlaneInterersFromWindow( float window_sp_x, float window_sp_y,
											const Vector4 &plane,
											bool *does_intersectp )
{
	Vector3 ray_from;
	Vector3 ray_dir;
	M5D::GetHRayFromWinPos( window_sp_x, window_sp_y, _viewport,
						GetMatrix(),//.GetBackTransformed(),
						GetProjMatrix(),
						ray_from, ray_dir );

	return getHRayPlaneIntersect( ray_from, ray_dir, plane, does_intersectp );
}

//==================================================================
void Camera::UpdateGlobalTransform()
{
	Model::UpdateGlobalTransform();

	_cur_cam_world_m = GetMatrix();//.GetBackTransformed();
//	_cur_cam_world_33_m._m14 = 0;
//	_cur_cam_world_33_m._m24 = 0;
//	_cur_cam_world_33_m._m34 = 0;
	((Vector4 *)_cur_cam_world_33_m._r)[0] = Vector4( _cur_cam_world_m.GetRow3(0)->GetNormalized(), 0);
	((Vector4 *)_cur_cam_world_33_m._r)[1] = Vector4( _cur_cam_world_m.GetRow3(1)->GetNormalized(), 0);
	((Vector4 *)_cur_cam_world_33_m._r)[2] = Vector4( _cur_cam_world_m.GetRow3(2)->GetNormalized(), 0);
	_cur_cam_world_33_m.GetRow4(3)->Set( 0, 0, 0, 1 );

	_cur_proj_world_m = GetProjMatrix() * _cur_cam_world_m;
}

};